package download

import (
	"fmt"
	"io"
	"net/http"
	"os"
	"sort"
	"strconv"
	"strings"
	"sync"
	"time"

	"rds-mysql-downloader/config"

	"rds-mysql-downloader/queue"

	openapi "github.com/alibabacloud-go/darabonba-openapi/client"
	rds20140815 "github.com/alibabacloud-go/rds-20140815/v2/client"
	"github.com/alibabacloud-go/tea/tea"
	"github.com/go-kit/log"
	"github.com/go-kit/log/level"
	"github.com/go-redis/redis/v8"
)

type AliyunMySQL struct {
	Logger log.Logger
}

var jobAndBackupID = struct {
	sync.RWMutex
	downLoadJobAndBackupIds map[string]int32
}{downLoadJobAndBackupIds: make(map[string]int32)}

// NewBackup
func NewBackup(logger log.Logger) *AliyunMySQL {
	return &AliyunMySQL{Logger: logger}
}

// FetchBackupList 获取备份集列表，返回map， map[instanceid]timeMaps[jobid+time]backupurl
func (a *AliyunMySQL) FetchBackupList(publicKey, privateKey string) map[string]map[string]string {
	timeMaps := make(map[string]string)
	urlMaps := make(map[string]map[string]string)
	//_, backupids, err := a.getBackupJobStatus(config.ConfigInfo.Keys.PublicKey, config.ConfigInfo.Keys.PrivateKey)
	err := a.getBackupJobStatus(config.ConfigInfo.Keys.PublicKey, config.ConfigInfo.Keys.PrivateKey)
	if err != nil {
		level.Error(a.Logger).Log("msg", err)
		return urlMaps
	}
	rdsConfig := &openapi.Config{
		// 您的AccessKey ID
		AccessKeyId: tea.String(publicKey),
		// 您的AccessKey Secret
		AccessKeySecret: tea.String(privateKey),
	}
	rdsConfig.Endpoint = tea.String("rds.aliyuncs.com")
	client, err := rds20140815.NewClient(rdsConfig)
	if err != nil {
		return urlMaps
	}
	for _, insid := range config.ConfigInfo.Global.InstanceID {
		for bkid, jbid := range jobAndBackupID.downLoadJobAndBackupIds {
			describeBackupsRequest := &rds20140815.DescribeBackupsRequest{
				DBInstanceId: tea.String(insid),
				BackupStatus: tea.String(config.ConfigInfo.Backup.BackupStatus),
				BackupMode:   tea.String(config.ConfigInfo.Backup.BackupMode),
				BackupId:     tea.String(bkid),
			}
			// 云平台的数据同步需要时间，所以此处循环去查询直至错误或查找到备份数据
			for {
				resp, err := client.DescribeBackups(describeBackupsRequest)
				if err != nil {
					break
				}
				if len(resp.Body.Items.Backup) != 0 {
					if config.ConfigInfo.Backup.BackupDownloadNetType == "Out" {
						timeMaps[fmt.Sprint(jbid)+"-"+strings.Replace(*resp.Body.Items.Backup[0].BackupStartTime, ":", "_", -1)] = *resp.Body.Items.Backup[0].BackupDownloadURL
					} else {
						timeMaps[fmt.Sprint(jbid)+"-"+strings.Replace(*resp.Body.Items.Backup[0].BackupStartTime, ":", "_", -1)] = *resp.Body.Items.Backup[0].BackupIntranetDownloadURL
					}
					urlMaps[*resp.Body.Items.Backup[0].DBInstanceId] = timeMaps
					break
				}
				time.Sleep(5 * time.Second)
			}
		}
	}
	return urlMaps
}

// Download 下载备份
func (a *AliyunMySQL) Download(urlMaps map[string]map[string]string) {
	var wg sync.WaitGroup
	for instance, maps := range urlMaps {
		for id, url := range maps {
			wg.Add(1)
			level.Info(a.Logger).Log("msg", "begining to download backup", "backupID", id)
			go wget(config.ConfigInfo.Backup.BackupMethod, id, instance, url, config.ConfigInfo.Redis.AlreadyDownloadJobs, strings.Split(id, "-")[0], a.Logger, &wg)
		}
	}
	wg.Wait()
}

// Create 创建backup副本集
func (a *AliyunMySQL) Create(publicKey, privateKey string) error {
	bakid := make(map[string]string)
	rdsConfig := &openapi.Config{
		// 您的AccessKey ID
		AccessKeyId: tea.String(publicKey),
		// 您的AccessKey Secret
		AccessKeySecret: tea.String(privateKey),
	}
	// 访问的域名
	rdsConfig.Endpoint = tea.String("rds.aliyuncs.com")
	// 超时设置，该产品部分接口调用比较慢，请您适当调整超时时间。
	rdsConfig.ReadTimeout = tea.Int(50000)
	rdsConfig.ConnectTimeout = tea.Int(50000)
	client, err := rds20140815.NewClient(rdsConfig)
	if err != nil {
		return err
	}
	for _, id := range config.ConfigInfo.Global.InstanceID {
		createBackupRequest := &rds20140815.CreateBackupRequest{
			DBInstanceId:   tea.String(id),
			BackupStrategy: tea.String(config.ConfigInfo.Backup.BackupStrategy),
			BackupMethod:   tea.String(config.ConfigInfo.Backup.BackupMethod),
		}
		resp, err := client.CreateBackup(createBackupRequest)
		if err != nil {
			return err
		}
		// {"rm----xxx": "xxxxid"}
		bakid[id] = *resp.Body.BackupJobId
		level.Info(a.Logger).Log("backupJobID", *resp.Body.BackupJobId)
	}

	// 写入redis
	err = queue.Hset(config.ConfigInfo.Redis.CreatedBackupJobs, bakid, config.ConfigInfo.Redis.Expire*time.Second)
	if err != nil {
		return err
	}
	for _, id := range bakid {
		level.Info(a.Logger).Log("command", "hset", "key", config.ConfigInfo.Redis.CreatedBackupJobs, "jobid", id, "expiration", config.ConfigInfo.Redis.Expire*time.Second)
	}

	return nil
}

// getBackupJobStatus 查询备份job的当前工作状态
func (a *AliyunMySQL) getBackupJobStatus(publicKey, privateKey string) error {
	var wg sync.WaitGroup
	needDolnloadJobs := []int32{}
	// 备份集ID 需要通过此id来确认唯一备份
	backupIds := []string{}
	// 存储jobid和backupid的映射关系 downLoadJobAndBackupIds[backupid] = jobid

	rdsConfig := &openapi.Config{
		// 您的AccessKey ID
		AccessKeyId: tea.String(publicKey),
		// 您的AccessKey Secret
		AccessKeySecret: tea.String(privateKey),
	}
	// 访问的域名
	rdsConfig.Endpoint = tea.String("rds.aliyuncs.com")
	client, err := rds20140815.NewClient(rdsConfig)
	if err != nil {
		return err
	}
	isDwonloadID, err := queue.Lrange(config.ConfigInfo.Redis.AlreadyDownloadJobs)
	if err == redis.Nil {
		level.Warn(a.Logger).Log("msg", "maybe it is the first time running this program")
	} else if err != nil {
		return err
	}
	fmt.Println("isdownload  = ", isDwonloadID)
	if len(isDwonloadID) == 0 {
		isDwonloadID = append(isDwonloadID, "firstDownload")
	}
	jobsInHash := queue.HgetAll(config.ConfigInfo.Redis.CreatedBackupJobs)
	for instance, jobid := range jobsInHash {
		fmt.Println("jobsInHash debug->", instance, jobid)
		// 遍历已下载列表，和hgetall获取到的数据做比对
		index := sort.SearchStrings(isDwonloadID, jobid)
		if index < len(isDwonloadID)-1 {
			level.Warn(a.Logger).Log("instance", instance, "job", jobid, "warn", "alredy download!")
		} else {
			wg.Add(1)
			jid, err := strconv.ParseInt(jobid, 10, 32)
			if err != nil {
				return err
			}
			describeBackupTasksRequest := &rds20140815.DescribeBackupTasksRequest{
				DBInstanceId: tea.String(instance),
				BackupJobId:  tea.Int32(int32(jid)),
			}
			go statusLoop(client, describeBackupTasksRequest, &needDolnloadJobs, &backupIds, a.Logger, &wg)
			time.Sleep(60 * time.Second)
		}
	}
	wg.Wait()
	return nil
}

// BackupJobStatus 获取备份工作完成时间
func (a *AliyunMySQL) BackupJobStatus(publicKey, privateKey string) error {
	var wg sync.WaitGroup
	needDolnloadJobs := []int32{}
	// 备份集ID 需要通过此id来确认唯一备份
	backupIds := []string{}
	// 存储jobid和backupid的映射关系 downLoadJobAndBackupIds[backupid] = jobid

	rdsConfig := &openapi.Config{
		// 您的AccessKey ID
		AccessKeyId: tea.String(publicKey),
		// 您的AccessKey Secret
		AccessKeySecret: tea.String(privateKey),
	}
	// 访问的域名
	rdsConfig.Endpoint = tea.String("rds.aliyuncs.com")
	client, err := rds20140815.NewClient(rdsConfig)
	if err != nil {
		return err
	}
	isDwonloadID, err := queue.Lrange(config.ConfigInfo.Redis.AlreadyDownloadJobs)
	if err == redis.Nil {
		level.Warn(a.Logger).Log("msg", "maybe it is the first time running this program")
	} else if err != nil {
		return err
	}
	//fmt.Println("isdownload  = ", isDwonloadID)
	if len(isDwonloadID) == 0 {
		isDwonloadID = append(isDwonloadID, "firstDownload")
	}
	jobsInHash := queue.HgetAll(config.ConfigInfo.Redis.CreatedBackupJobs)
	for instance, jobid := range jobsInHash {
		fmt.Println("jobsInHash debug->", instance, jobid)
		index := sort.SearchStrings(isDwonloadID, jobid)
		if index < len(isDwonloadID)-1 {
			level.Warn(a.Logger).Log("instance", instance, "job", jobid, "warn", "alredy download!")
		} else {
			wg.Add(1)
			jid, err := strconv.ParseInt(jobid, 10, 32)
			if err != nil {
				return err
			}
			describeBackupTasksRequest := &rds20140815.DescribeBackupTasksRequest{
				DBInstanceId: tea.String(instance),
				BackupJobId:  tea.Int32(int32(jid)),
			}
			level.Info(a.Logger).Log("notice", "等待阿里云同步备份状态，如失败请重试")
			time.Sleep(60 * time.Second)
			// go statusLoop(client, describeBackupTasksRequest, &needDolnloadJobs, &backupIds, a.Logger, &wg)
			go func(client *rds20140815.Client, request *rds20140815.DescribeBackupTasksRequest, needDolnloadJobs *[]int32, backupIds *[]string, logger log.Logger, wg *sync.WaitGroup) {
				defer wg.Done()
				for {
					resp, err := client.DescribeBackupTasks(request)
					if err != nil {
						fmt.Println("describeBackupTask error", err)
						return
					}
					if *resp.Body.Items.BackupJob[0].BackupStatus == "Failed" {
						level.Error(a.Logger).Log("instance", request.DBInstanceId, "jobid", request.BackupJobId, "backupStatus", "Failed")
						return
					}
					if *resp.Body.Items.BackupJob[0].BackupProgressStatus == "Finished" && *resp.Body.Items.BackupJob[0].BackupStatus == "Finished" {
						level.Info(logger).Log("status", "Finished", "instance", request.DBInstanceId, "jobid", request.BackupJobId, "backupIds", *resp.Body.Items.BackupJob[0].BackupId, "progressStatus", *resp.Body.Items.BackupJob[0].BackupProgressStatus, "backupStatus", *resp.Body.Items.BackupJob[0].BackupStatus)
						return
					} else {
						level.Info(logger).Log("instance", request.DBInstanceId, "jobid", request.BackupJobId, "backupIds", *resp.Body.Items.BackupJob[0].BackupId, "progressStatus", *resp.Body.Items.BackupJob[0].BackupProgressStatus, "backupStatus", *resp.Body.Items.BackupJob[0].BackupStatus)
						time.Sleep(20 * time.Second)
					}
				}
			}(client, describeBackupTasksRequest, &needDolnloadJobs, &backupIds, a.Logger, &wg)
		}
	}
	wg.Wait()
	return nil
}

//statusLoop 协程循环查询jobid的运行状态
func statusLoop(client *rds20140815.Client, request *rds20140815.DescribeBackupTasksRequest, needDolnloadJobs *[]int32, backupIds *[]string, logger log.Logger, wg *sync.WaitGroup) {
	defer wg.Done()
	for {
		resp, err := client.DescribeBackupTasks(request)
		if err != nil {
			fmt.Println("describeBackupTask error", err)
			return
		}
		/* BackupStatus
		NoStart：未开始
		Checking：检查备份
		Preparing：准备备份
		Waiting：等待备份
		Uploading：上传备份
		Finished：完成备份
		Failed：备份失败
		*/
		if *resp.Body.Items.BackupJob[0].BackupStatus == "Failed" {
			return
		}
		if *resp.Body.Items.BackupJob[0].BackupProgressStatus != "Finished" {
			level.Warn(logger).Log("instance", request.DBInstanceId, "jobid", request.BackupJobId, "progressStatus", *resp.Body.Items.BackupJob[0].BackupProgressStatus, "JobStatus", *resp.Body.Items.BackupJob[0].BackupStatus)
			time.Sleep(20 * time.Second)
		} else if *resp.Body.Items.BackupJob[0].BackupProgressStatus == "Finished" && *resp.Body.Items.BackupJob[0].BackupStatus == "Finished" {
			level.Info(logger).Log("instance", request.DBInstanceId, "jobid", request.BackupJobId, "backupIds", *resp.Body.Items.BackupJob[0].BackupId, "progressStatus", *resp.Body.Items.BackupJob[0].BackupProgressStatus)
			jobAndBackupID.RLock()
			jobAndBackupID.downLoadJobAndBackupIds[*resp.Body.Items.BackupJob[0].BackupId] = *request.BackupJobId
			jobAndBackupID.RUnlock()
			return
		} else {
			level.Warn(logger).Log("instance", request.DBInstanceId, "jobid", request.BackupJobId, "progressStatus", *resp.Body.Items.BackupJob[0].BackupProgressStatus, "JobStatus", *resp.Body.Items.BackupJob[0].BackupStatus)
		}
	}
}

// wget 下载mysql备份 并将成功下载过的备份集id写入redis
func wget(name string, flagid string, instance string, url string, redisKey string, backupid string, logger log.Logger, wg *sync.WaitGroup) {
	defer wg.Done()
	var suffixName string
	if name == "Logical" {
		suffixName = ".tgz"
	} else if name == "Physical" {
		suffixName = ".xb"
	}
	file, err := os.OpenFile(config.ConfigInfo.Backup.BackupStorePath+instance+"_mysql_backup_"+flagid+suffixName, os.O_CREATE|os.O_APPEND|os.O_RDWR, 0666)
	if err != nil {
		level.Error(logger).Log("instanceid", instance, "open file error", err)
		return
	}
	resp, err := http.Get(url)
	if err != nil {
		level.Error(logger).Log("instanceid", instance, "get url error", err)
		return
	}
	_, err = io.Copy(file, resp.Body)
	if err != nil {
		level.Error(logger).Log("instanceid", instance, "download err", err, "file", file.Name())
		return
	}
	file.Close()
	resp.Body.Close()
	err = queue.Lpush(redisKey, backupid)
	if err != nil {
		level.Error(logger).Log("msg", "LPUSH error", "err", err)
		return
	}
	level.Info(logger).Log("command", "LPUSH", "key", redisKey, "id", backupid)
}
